/*
 * SmartCam master processor library
 * Coprocessor mapping determination.
 */

#define _XOPEN_SOURCE 500

#ifndef PSOS
#include <values.h>
#endif
#include <math.h>
#include <log.h>
#include "globals.h"
#include "dispatch.h"
#include "evaluate.h"
#include "greedy.h"
#include "combinatorial.h"
#include "mapping.h"
#include "settings.h"

static int map = -1;
int map_uses_affinity = 0;
table_t scm_procs, scm_ops, scm_streams; /* owned, indexed */

scos_mutex_t scm_lock;

void scMapInit()
{
  int id=-1, id2=-1, changed;
  scm_proc_t *sp, *dp, *op;
  scm_proc_t *p;
  scm_proclist_t *gateway;
  scm_resource_t *pr;

  scs_target_t *t;
  scs_target_resource_t *tr;
  scs_link_t *l;

  lprintf(STREAM(scl.notice), "Initializing operation mapping");

  if (settings.avfile)
    scMapReadAvailability(settings.avfile);

  if (settings.benchfile)
    scMapReadBenchmarks(settings.benchfile);
    
  /* Create strucutres */
  abortOnNegative(STREAM(scl.err), tblcreate(&scm_procs, sizeof(scm_proc_t)));
  abortOnNegative(STREAM(scl.err), tblcreate(&scm_ops, sizeof(scm_op_t)));
  abortOnNegative(STREAM(scl.err), tblcreate(&scm_streams, sizeof(scm_stream_t)));

  /* Create processors */
  while (tblget(&scs_targets, id = tblgetnexti(&scs_targets, id), &t))
  {
    if (!t->address)
      continue;

    abortOnNegative(STREAM(scl.err), tbladdnewi(&scm_procs, &p, t->id));

    abortOnNegative(STREAM(scl.err), tblcreate(&p->operations, sizeof(scm_op_t)));
    abortOnNegative(STREAM(scl.err), tblcreate(&p->streams, sizeof(scm_stream_t)));
    abortOnNegative(STREAM(scl.err), tblcreate(&p->routes, sizeof(scm_proc_t)));
    abortOnNegative(STREAM(scl.err), tblcreate(&p->resources, sizeof(scm_resource_t)));

    p->target = t;
    tblget(&sc_procs, p->target->id, &p->processor);

    while (tblgetnextelement(&t->resources, &id2, &tr))
    {
      abortOnNegative(STREAM(scl.err), tbladdnewi(&p->resources, &pr, tr->resource->id));
      pr->target_resource = tr;
    }

    if (!tblget(&t->operations, TRANSPORT, &p->transport_operation))
    {
      abortErrorl((STREAM(scl.err), "Couldn't find transport operation for target %d ('%s')",
                  t->id, t->name));
    }
  }
 
  /* Setup routing tables */
  do
  {
    changed = 0;
    
    while (tblgetnextelement(&scs_links, &id, &l))
    {
      abortOnNULL(STREAM(scl.err), tblget(&scm_procs, l->source->id, &sp));
      abortOnNULL(STREAM(scl.err), tblget(&scm_procs, l->destination->id, &dp));

      /* If there's a link from A to B, A can reach B via B */
      /* Note: this assures the first gateway is always direct, if possible */
      if (!tblget(&sp->routes, dp->target->id, NULL))
      {	
	abortOnNegative(STREAM(scl.err), tbladdnewi(&sp->routes, &gateway, dp->target->id));
	gateway->proc = dp;
	changed = 1;
      }

      /* If there's a link from A to B, and B can reach C,
       * A can reach C via B */
      while (tblgetnextelement(&dp->routes, &id2, NULL))
      {
	abortOnNULL(STREAM(scl.err), tblget(&scm_procs, id2, &op));

	if (op != sp)
	{
	  if (!tblget(&sp->routes, op->target->id, &gateway))
	  {
	    /* Don't know how to get to this C yet */
	    abortOnNegative(STREAM(scl.err),
			    tbladdnewi(&sp->routes, &gateway, op->target->id));
	    gateway->proc = dp;
	    changed = 1;
	  }
	  else
	  {
	    /* See if we know this gateway to C already */
	    for (;;)
	    {
	      if (gateway->proc == dp)
		break;
	      else if (!gateway->next)
		break;
	      else
		gateway = gateway->next;
	    }

	    if (gateway->proc != dp)
	    {
	      /* Add B as a gateway to C */
	      gateway->next = calloc(1, sizeof(scm_proclist_t));
	      gateway->next->proc = dp;
	    }
	  }
	}
      }
    }
  } while (changed);

  lprintf(STREAM(scl.crawl), "Routing tables");

  while (tblgetnextelement(&scm_procs, &id, &sp))
  {
    lprintf(STREAM(scl.crawl), "%s (routing=%d)", sp->target->name, sp->target->routing);

    while (tblgetnextelement(&sp->routes, &id2, &gateway))
    {
      abortOnNULL(STREAM(scl.err), tblget(&scm_procs, id2, &dp))

      lprintf(STREAM(scl.crawl), "-> %s", dp->target->name);
      
      while (gateway)
      {
	lprintf(STREAM(scl.crawl), "   via %s", gateway->proc->target->name);
	gateway = gateway->next;
      }
    }
  }

  scos_mutex_init(&scm_lock);
}

void scMapReadAvailability(char *avfile)
{
  char buf[PATH_MAX];
  scs_target_t *t;
  int id=-1;

  lprintf(STREAM(scl.info), "Reading availability from %s", avfile);

  scSetRead(avfile);
  scSetIndex();

  while (tblgetnextelement(&scs_targets, &id, &t))
  {
    if (t->address)
    {
      scs_target_operation_t *o;
      int id=-1;
      
      sprintf(buf, "%s (%d; %s): ", t->name, t->id, t->driver);
      
      while (tblgetnextelement(&t->operations, &id, &o))
	sprintf(&buf[strlen(buf)], "%d ", o->operation->id);
      
      lprintf(STREAM(scl.debug), buf);
    }
  }
}

double scMapFindEnergyScaling(double f)
{
  double a   = 1.3; /* Velocity saturation at CMOS18 */
  double Vt  = 0.5; /* Typical threshold voltage at CMOS18 */
  double V18 = 1.8; /* Nominal supply voltage at CMOS18 */
  double F18 = pow((V18-Vt), a)/V18; /* Relative frequency at nominal settings */
  double V   = 1;   /* Initial guess for scaled voltage */
  double diff;
  
  /* Newton's method */
  do
  {
    diff = f - (pow((V-Vt), a)/V) / F18;  
    V += diff;
  } while (diff > 0.001);

  lprintf(STREAM(scl.info), "Supply voltage for f=%f is %f", f, V);
  
  return pow((V/V18), 2);
}

void scMapReadBenchmarks(char *benchfile)
{
  scs_target_t *t;   
  int id=-1;
    
  lprintf(STREAM(scl.info), "Reading benchmarks from %s", benchfile);

  scSetRead(benchfile);
  scSetIndex();

  lprintf(STREAM(scl.info), "Applying frequency scaling");

  while (tblgetnextelement(&scs_targets, &id, &t))
  {
    scs_target_operation_t *to;
    int id=-1;

    if (t->frequency > 0)
    {
      double energy = scMapFindEnergyScaling(t->frequency);
      
      lprintf(STREAM(scl.info), "%s scaled by f=%f, E=%f", t->name, t->frequency, energy);
      
      t->power *= energy*t->frequency;

      while (tblgetnextelement(&t->operations, &id, &to))
      {
         to->time /= t->frequency;
         to->energy *= energy;
      }
    }
  }
}

void scMapNextProcessor(void)
{
  map = tblgetnexti(&sc_procs, map);
  if (map < 0)
    map = tblgetnexti(&sc_procs, map);

  abortOnError(STREAM(scl.err), map < 0);
}

/* *** TODO ***
 * scMapOperation(, 0)
 * should be made into a separate function that quickly determines
 * whether a graph is mappable, without finding the best
 * mapping
 */
int scMapOperation(sc_op_t *o, int apply)
{
  sc_proc_t *p;
  scm_proc_t *mp;
  scm_op_t *mo;
  sc_op_t *so;
  int id=-1, fail, graph=scs_mapper.graph;

  scos_mutex_lock(&scm_lock);

  lprintf(STREAM(scl.crawl), "Mapping %s operation %d", o->text, o->id);

  if (o->affinity != -1)
    map_uses_affinity = 1;

  if (!settings.benchfile)
  {
    /* Don't have any benchmarking information. */
    if (scMapIsOperationMappable(o))
    {
      if (!settings.avfile)
      {
	/* Don't know which processor can run what. Just do round-robin */
	scMapNextProcessor();

	abortOnNULL(STREAM(scl.err), tblget(&sc_procs, map, &p));
	
	scMapApplyProcessorAssignment(o, p);
      }
      else
      {
	/* Resource-aware round-robin scheduling */
	scMapBuildCurrent();

	abortOnNULL(STREAM(scl.err), tblget(&scm_ops, o->id, &mo));
	
	/* Initialize mapping to next processor that can execute the operation */
	while (tblgetnextelement(&scm_procs, &map, &mp))
	{
	  if (tblget(&mp->target->operations, mo->operation->id, NULL))
	    if (!scMapAssignProcessor(mo, mp))
	      break;
	}

	if (!mp)
	{
	  while (tblgetnextelement(&scm_procs, &map, &mp))
	  {
	    if (tblget(&mp->target->operations, mo->operation->id, NULL))
	      if (!scMapAssignProcessor(mo, mp))
		break;
	  }
	}

	if (!mp)
	{
	  scMapDestroy();
	  scos_mutex_unlock(&scm_lock);
	  return 1;
	}

	scMapApplyProcessorAssignments();

	scMapDestroy();
      }

      /* Because we don't map everything immediately, try to map all
      >> unmapped operations every time...
      */
/*
      while (tblget(&sc_ops, id = tblgetslicenexti(&sc_ops, 1, id), &so))
        scMapOperation(so);
*/
      while (tblget(&sc_ops, id = tblgetnexti(&sc_ops, id), &so))
        if (!so->processor) scMapOperation(so, apply);
    }
  }
  else
  {
    tmr_t timer = tmrInit();
    int applications;
    scm_op_t *o;

    applications = scMapBuildCurrent() - scs_mapper.delay;

    tmrStart(&timer);

    if (scs_mapper.type == SC_MAP_COMBINATORIAL)
      fail = scMapCombinatorial();
    else if (scs_mapper.type == SC_MAP_GREEDY)
    {
      if (!apply && applications <= 0)
        fail = scMapGreedy(0);
      else
        fail = scMapGreedy(1);
    }

    else
      abortErrorl((STREAM(scl.err), "Unknown mapper type '%d'", scs_mapper.type));

    tmrStop(&timer);

    lprintf(STREAM(scl.info), "Mapping took %f us", tmrGetValueInUs(&timer));
    
    scEvlMapping(1);
    if (logEdit(scl.debug, LOG_STREAM_GET_ENABLED) ||
        (logEdit(scl.info, LOG_STREAM_GET_ENABLED) && apply))
      scEvlPrint();
      
    if (apply)
    {
      scs_mapper.graph = SC_GRAPH_TIG;
      printf("%% TIG prediction: %f\n", 1.0/scEvlMapping(1));
      scs_mapper.graph = SC_GRAPH_TDG;
      printf("%% TDG prediction: %f\n", 1.0/scEvlMapping(1));
      scs_mapper.graph = SC_GRAPH_DTIG;
      printf("%% DTIG prediction: %f\n", 1.0/scEvlMapping(1));
      scs_mapper.graph = graph;
      scEvlMapping(1);
    }
    
    tmrStart(&timer);

    if (!apply)
    {
      /* Unassign all processors after # applications */
      /* *** TAKE CARE: THIS WILL CAUSE SOME TRANSPORTS TO BE 
       * *** UNMAPPED, AND THUS TO BE ASSIGNED DIRECTLY
       */
      while (tblget(&scm_ops, id = tblgetnexti(&scm_ops, id), &o))
	if (!o->fixed && o->processor)
	{
	  if (applications > 0)
	    applications--;
	  else
	    scMapUnassignProcessor(o);
	}
    }

    scMapApplyProcessorAssignments();

    scMapDestroy();

    tmrStop(&timer);
    
    lprintf(STREAM(scl.crawl), "Application took %f us", tmrGetValueInUs(&timer));

    scos_mutex_unlock(&scm_lock);
    return fail;
  }

  scos_mutex_unlock(&scm_lock);
  return 0;
}

void scMapApplyProcessorAssignments(void)
{
  int id=-1;
  sc_op_t *so;
  sc_proc_t *sp;
  sc_var_t *v;
  scm_stream_t *s;
  scm_op_t *o;

  while (tblgetnextelement(&scm_streams, &id, &s))
  {
    tblget(&sc_vars, id, &v);
    scMapApplyTransportAssignment(s, v);
  }

  /* Find all unfixed, mapped operations */
  while (tblget(&scm_ops, id = tblgetnexti(&scm_ops, id), &o))
  {
    if (!o->fixed && o->processor)
    {
      abortOnNULL(STREAM(scl.err), tblget(&sc_ops, o->id, &so));
      abortOnNULL(STREAM(scl.err), sp = o->processor->processor);
      scMapApplyProcessorAssignment(so, sp);
    }
  }
}

void scMapApplyProcessorAssignment(sc_op_t *o, sc_proc_t *p)
{
  int id=-1;
  sc_arg_t *a;

  /* Do nothing if operation was already mapped */
  if (o->processor) return;

  /* Map operation */
  o->processor = p;
  abortOnNegative(STREAM(scl.err), tbladd(&p->operations, o));
//  tblswitchslice(&sc_ops, o->id, 1, 0);

  /* Determine stream variable buffer sizes */
  while (tblget(&o->arguments, id = tblgetnexti(&o->arguments, id), &a))
    if (a->instance->variable->type == SC_VAR_STREAM)
    {
      scMapAssignTransportsDirect(a->instance->variable);
      a->buffer = MAX(a->buffer, p->bufsize);
    }

  lprintf(STREAM(scl.info), "%s operation %d mapped to %s", o->text, o->id, p->name);

  /* Dispatch */
  scDisOperation(o);
}

void scMapApplyTransportAssignment(scm_stream_t *ms, sc_var_t *v)
{
  int id=-1;
  scm_transport_t *mt;
  sc_transport_t *st;

  while (tblgetnextelement(&ms->transports, &id, &mt))
  {
    if (!tblget(&v->transports, id, NULL))
    {
      tbladdnewi(&v->transports, &st, id);

      st->source = mt->source->processor;
      st->destination = mt->destination->processor;
      st->weight = 1;
    }
  }
}

void scMapAssignTransportsDirect(sc_var_t *s)
{
  int id=-1;
  sc_transport_t *t;
  sc_arg_t *o;
  scm_proc_t *sp, *ip, *dp;
  scm_proc_t *source, *destination;
  scm_proclist_t *gateway;

  /* Can't know links if we don't know where it's coming from */
  if (!s->producer || ! s->producer->operation->processor)
    return;

  abortOnNULL(STREAM(scl.err),
	      tblget(&scm_procs, s->producer->operation->processor->id, &source));

  lprintf(STREAM(scl.crawl), "Directly assigning transports of %d (from %s)",
	  s->id, source->target->name); 

  while (tblgetnextelement(&s->consumers, &id, &o))
  {
    if (!o->operation->processor) continue;

    tblget(&scm_procs, o->operation->processor->id, &destination);

    if (destination && source != destination &&
	!tblget(&s->transports, destination->target->id, NULL))
    {
      lprintf(STREAM(scl.crawl), "to %s", destination->target->name); 

      sp = ip = source;
      do
      {
	/* Get gateway for destination */
	abortOnNULL(STREAM(scl.err),
		    tblget(&ip->routes, destination->target->id, &gateway));
	dp = gateway->proc;

	if (!tblget(&s->transports, dp->target->id, &t))
	{
	  if (dp->processor)
	  {
	    /* Add route from sp to dp */
	    abortOnNegative(STREAM(scl.err),
			    tbladdnewi(&s->transports, &t, dp->target->id));
	    t->source = sp->processor;
	    t->destination = dp->processor;
	    
	    /* *** Warning: weight is not determined correctly here *** */
	    t->weight = 1;

	    sp = dp;
	    
	    lprintf(STREAM(scl.crawl), "source routing via %s", dp->target->name);
	  }
	  else
	    lprintf(STREAM(scl.crawl), "implicit routing via %s", dp->target->name);
	}

	ip = dp;
      } while (dp != destination);
    }
  }
}

int scMapBuildCurrent(void)
{
  int id=-1, id2=-1, index, dof=0;
  sc_op_t      *so;
  sc_arg_t     *sa;
  scs_operation_argument_t *soa;

  scm_op_t     *o;
  scm_proc_t   *p;
  scm_stream_t *s;
  scm_arg_t    *a;
  
  tmr_t        timer = tmrInit();  

  lprintf(STREAM(scl.debug), "Building current operation structure");

  tmrStart(&timer);

  /* Find operations which are currently in flight */
  while (tblget(&sc_ops, id = tblgetnexti(&sc_ops, id), &so))
  {
    if (!so->finished && scMapIsOperationMappable(so))
    {
      /* Add operation to our constellation */
      abortOnNegative(STREAM(scl.err), tbladdnewi(&scm_ops, &o, so->id));
      o->id = so->id;
      o->affinity = so->affinity;

      if (!tblget(&scs_operations, so->type, &o->operation))
      {
        abortErrorl((STREAM(scl.err), "Couldn't find operation type %d of operation %d ('%s')",
                    so->type, so->id, so->text));        
      }

      abortOnNegative(STREAM(scl.err), tblcreate(&o->streams, sizeof(scm_stream_t)));
      abortOnNegative(STREAM(scl.err), tblcreate(&o->arguments, sizeof(scm_arg_t)));

      lprintf(STREAM(scl.crawl), "Adding %s operation %d type %d line %d",
	      o->operation->name, o->id, o->operation->id, o->operation->line);

      /* Find operation arguments. id2 will be -1 at end of loop. */
      while (tblget(&so->arguments, id2 = tblgetnexti(&so->arguments, id2), &sa))
      {
	if (sa->instance->variable->type == SC_VAR_STREAM)
	{
	  /* If stream doesn't exit yet, create it */
	  if (!tblget(&scm_streams, sa->instance->variable->id, &s))
	  {
	    abortOnNegative(STREAM(scl.err),
			    tbladdnewi(&scm_streams, &s, sa->instance->variable->id));
	    s->id = sa->instance->variable->id;
	    abortOnNegative(STREAM(scl.err), tblcreate(&s->consumers, sizeof(scm_op_t)));
	    abortOnNegative(STREAM(scl.err), tblcreate(&s->processors, sizeof(scm_proc_t)));
	    abortOnNegative(STREAM(scl.err), tblcreate(&s->transports, sizeof(scm_transport_t)));
	    abortOnNegative(STREAM(scl.err), tblcreate(&s->alltransports, sizeof(scm_transport_t)));
	  }

	  /* Add stream to operation arguments */
	  abortOnNegative(STREAM(scl.err), index = tbladd(&o->streams, s));

	  /* Look up stream size */
	  tblget(&o->operation->arguments, sa->id, &soa);
	  if (soa) s->bytes = soa->length;
	  
	  /* Add operation to stream referencees */
	  abortOnNegative(STREAM(scl.err), tbladdnewi(&o->arguments, &a, id2));
	  a->operation = o;

	  if (sa->intent == SC_INTENT_OUT)
	    s->producer = a;
	  else
	    abortOnNegative(STREAM(scl.err), tbladd(&s->consumers, a));
	}
      }

      if (so->processor)
      {
	/* Operation is already mapped. Get processor. */
	abortOnNULL(STREAM(scl.err), tblget(&scm_procs, so->processor->id, &p));

	/* Perform mapping */
	abortOnError(STREAM(scl.err), scMapAssignProcessor(o, p));

	/* Set to fixed so we don't try to change the mapping */
	o->fixed = 1;
      }
      else
	dof++;
    }
  }  

  tmrStop(&timer);

  lprintf(STREAM(scl.debug), "Building took %f", tmrGetValueInUs(&timer));

  return dof;
}

int scMapAssignProcessor(scm_op_t *o, scm_proc_t *p)
{
  int id=-1;
  scm_stream_t *s;
  scm_resource_t *r;
  scm_arg_t *a;

  scs_target_operation_resource_t *tor;
  scs_target_operation_argument_t *toa;

  lprintf(STREAM(scl.crawl), "Assigning %s operation %d to processor %s",
	  o->operation->name, o->id, p->target->name);

  /* Find target operation */
  abortOnNULL(STREAM(scl.err),
	      tblget(&p->target->operations, o->operation->id, &o->target_operation));

  if (settings.benchfile && o->target_operation->time < 0)
  {
    lprintf(STREAM(scl.crawl), "Couldn't assign because operation has not been benchmarked");
    return 1;
  }

  /* Add references */
  o->processor = p;
  abortOnNegative(STREAM(scl.err), tbladdi(&p->operations, o, o->id));

  /* Check resources */
  while (tblgetnextelement(&o->target_operation->resources, &id, &tor))
  {
    abortOnNULL(STREAM(scl.err), tblget(&o->processor->resources, id, &r));
    if (r->count + tor->count > r->target_resource->count)
    {
      lprintf(STREAM(scl.crawl), "Couldn't assign because resource '%s' is overcommitted",
	      r->target_resource->resource->name);

      o->target_operation = NULL;
      o->processor = NULL;
      abortOnNegative(STREAM(scl.err), tbldel(&p->operations, o->id, 0));
      return 1;
    }
  }

  /* Apply resources */
  while (tblgetnextelement(&o->target_operation->resources, &id, &tor))
  {
    abortOnNULL(STREAM(scl.err), tblget(&o->processor->resources, id, &r));
    r->count += tor->count;
  }

  /* Run through operation stream arguments */
  while (tblgetnextelement(&o->arguments, &id, &a))
  {
    abortOnNULL(STREAM(scl.err), tblget(&o->target_operation->arguments, id, &toa));
    if (toa->timedist == SC_DIST_BULK)
      a->bulk = 1;
    else
      a->bulk = 0;
  }

  /* Run through operation streams */
  while (tblget(&o->streams, id = tblgetnexti(&o->streams, id), &s))
  {
    /* Assign stream to the appropriate links */
    if (scMapAssignTransports(s))
    {
      scMapUnassignProcessor(o);
      return 1;
    }
  }

  return 0;
}

int scMapAssignTransports(scm_stream_t *s)
{
  int id=-1, id2=-1, id3=-1;
  scm_transport_t *t;
  scm_arg_t *a;
  scm_proc_t *sp, *ip, *dp;
  scm_proc_t *source, *destination;
  scs_target_operation_resource_t *tor;
  scm_resource_t *res;
  scm_proclist_t *gateway;

  /* Can't know links if we don't know where it's coming from */
  if (!s->producer || ! s->producer->operation->processor)
    return 0;

  source = s->producer->operation->processor;

  lprintf(STREAM(scl.crawl), "Assigning transports of %d (from %s)",
	  s->id, source->target->name); 

  while (tblgetnextelement(&s->consumers, &id, &a))
  {
    destination = a->operation->processor;

    if (destination && source != destination &&
	!tblget(&s->transports, destination->target->id, NULL))
    {
      lprintf(STREAM(scl.crawl), "to %s", destination->target->name); 

      sp = ip = source;
      do
      {
	/* Add stream to ip, and vice versa  */
	if (!tblget(&s->processors, ip->target->id, NULL))
	{
	  tbladdi(&s->processors, ip, ip->target->id);
	  tbladdi(&ip->streams, s, s->id);
	}

	/* Get gateway for destination */
	if (!tblget(&ip->routes, destination->target->id, &gateway))
	{
          lprintf(STREAM(scl.debug),
    	          "Cannot find a route between %s and %s because %s doesn't specify gateway",
	          source->target->name, destination->target->name, ip->target->name);
	     
	  scMapUnassignTransports(s);
	  return 1;
	}

	dp = gateway->proc;

	if (dp != destination)
	{
	  id3 = -1;
	  /* Check resources. Note: no backtracking, thus only one level deep.
	   * It's enough to find an unused port though, and that's its primary use */
	  while (gateway)
	  {
	    dp = gateway->proc;

	    while (tblgetnextelement(&dp->transport_operation->resources, &id3, &tor))
	    {
	      abortOnNULL(STREAM(scl.err), tblget(&dp->resources, id3, &res));
	      if (res->count + tor->count > res->target_resource->count)
		break;
	    }
	    
	    if (id3 < 0)
	      /* All resources are available */
	      break;
	    
	    gateway = gateway->next;
	  }

	  if (!gateway)
	  {
	    lprintf(STREAM(scl.debug),
		    "Cannot find a route between %s and %s because of resource limitations after %s",
		    source->target->name, destination->target->name, ip->target->name);
	    
	    scMapUnassignTransports(s);
	    return 1;
	  }
	}

	if (tblget(&s->alltransports, dp->target->id, &t))
	{
#ifndef MULTICAST
          /* For unicast networks, stream will pass over this link again */
	  t->streams++;
#endif
	}
	else
	{
          abortOnNegative(STREAM(scl.err),
                          tbladdnewi(&s->alltransports, &t, dp->target->id));
	  abortOnNULL(STREAM(scl.err), tblget(&scm_procs, ip->target->id, &t->source));
	  abortOnNULL(STREAM(scl.err), tblget(&scm_procs, dp->target->id, &t->destination));
	  t->streams = 1;
	}

	if (!tblget(&s->transports, dp->target->id, &t))
	{
	  if ((dp->target->routing < 1) || settings.simulate)
	  {
	    /* Add route from sp to dp */
	    abortOnNegative(STREAM(scl.err),
			    tbladdnewi(&s->transports, &t, dp->target->id));
	    abortOnNULL(STREAM(scl.err), tblget(&scm_procs, sp->target->id, &t->source));
	    abortOnNULL(STREAM(scl.err),
			tblget(&scm_procs, dp->target->id, &t->destination));
	    t->streams = 1;

	    /* Apply resources */
	    id3 = -1;
	    while (tblgetnextelement(&sp->transport_operation->resources, &id2, &tor))
	    {
	      abortOnNULL(STREAM(scl.err), tblget(&sp->resources, id2, &res));
	      res->count += tor->count;
	    }

	    sp = dp;
	    
	    lprintf(STREAM(scl.crawl), "source routing via %s", dp->target->name);
	  }
	  else
	    lprintf(STREAM(scl.crawl), "implicit routing via %s", dp->target->name);
	}
	else
	{
	  /* Already know how to get here, and it's a nonrouting processor as there's
	   * a transport to it, so continue from here */
#ifndef MULTICAST
          /* For unicast networks, stream will pass over this link again */
	  t->streams++;
#endif
	  sp = dp;
	}

	ip = dp;
      } while (dp != destination);

      /* 11-07-2006: Add stream to destination processor as well */
      /* Add stream to ip, and vice versa  */
      if (!tblget(&s->processors, ip->target->id, NULL))
      {
	tbladdi(&s->processors, ip, ip->target->id);
	tbladdi(&ip->streams, s, s->id);
      }
    }
  }

  return 0;
}

void scMapUnassignProcessor(scm_op_t *o)
{
  int id=-1;
  scm_stream_t *s;
  scm_resource_t *r;

  scs_target_operation_resource_t *tor;

  lprintf(STREAM(scl.crawl), "Unassigning %s operation %d", o->operation->name, o->id);

  /* Can't unmap fixed operation */
  if (o->fixed)
    return;

  /* Find and remove resources */
  while (tblgetnextelement(&o->target_operation->resources, &id, &tor))
  {
    abortOnNULL(STREAM(scl.err), tblget(&o->processor->resources, id, &r));
    r->count -= tor->count;
  }

  /* Run through operation stream arguments */
  while (tblget(&o->streams, id = tblgetnexti(&o->streams, id), &s))
  {
    /* Remove stream from the appropriate links */
    scMapUnassignTransports(s);
  }

  /* Remove operation from its processor */
  abortOnNegative(STREAM(scl.err), tbldel(&o->processor->operations, o->id, 0));

  /* And the processor from this operation */
  o->processor = NULL;
}

void scMapUnassignTransports(scm_stream_t *s)
{
  int id=-1, id2=-1;
  scm_proc_t *p;
  scm_transport_t *t;
  scs_target_operation_resource_t *tor;
  scm_resource_t *r;

  lprintf(STREAM(scl.crawl), "Unassigning transports of %d", s->id);

  /* Remove resources */
  while (tblgetnextelement(&s->transports, &id, &t))
  {
    while (tblgetnextelement(&t->source->transport_operation->resources, &id2, &tor))
    {
      abortOnNULL(STREAM(scl.err), tblget(&t->source->resources, id2, &r));
      r->count -= tor->count;
    }
  }

  tblclear(&s->transports, 1);
  tblclear(&s->alltransports, 1);

  while (tblgetnextelement(&s->processors, &id, &p))
    tbldel(&p->streams, s->id, 0);

  tblclear(&s->processors, 0);
}

void scMapReset(void)
{
  int id = -1;
  scm_op_t *o;
  
  /* Unmap all non-fixed operations so we can try another configuration */
  while (tblget(&scm_ops, id = tblgetnexti(&scm_ops, id), &o))
    if (!o->fixed)
      scMapUnassignProcessor(o);
}

void scMapDestroy(void)
{
  int id=-1, id2=-1;
  scm_proc_t *p;
  scm_stream_t *s;
  scm_op_t *o;
  scm_resource_t *r;

  lprintf(STREAM(scl.debug), "Destroying operation structure");

  /* Processors (only clear internal tables) */
  while (tblget(&scm_procs, id = tblgetnexti(&scm_procs, id), &p))
  {
    abortOnNegative(STREAM(scl.err), tblclear(&p->operations, 0));
    abortOnNegative(STREAM(scl.err), tblclear(&p->streams, 0));
    
    while (tblgetnextelement(&p->resources, &id2, &r))
      r->count = 0;
  }
  
  /* Streams */
  while (tblget(&scm_streams, id = tblgetnexti(&scm_streams, id), &s))
  {
    abortOnNegative(STREAM(scl.err), tbldestroy(&s->consumers, 0));
    abortOnNegative(STREAM(scl.err), tbldestroy(&s->transports, 1));
    abortOnNegative(STREAM(scl.err), tbldestroy(&s->alltransports, 1));
    abortOnNegative(STREAM(scl.err), tbldestroy(&s->processors, 0));
  }
  abortOnNegative(STREAM(scl.err), tblclear(&scm_streams, 1));

  /* Operations */
  while (tblget(&scm_ops, id = tblgetnexti(&scm_ops, id), &o))
  {
    abortOnNegative(STREAM(scl.err), tbldestroy(&o->streams, 0));
    abortOnNegative(STREAM(scl.err), tbldestroy(&o->arguments, 1));
  }
  abortOnNegative(STREAM(scl.err), tblclear(&scm_ops, 1));
}

int scMapIsOperationMappable(sc_op_t *so)
{
  int id=-1;
  sc_arg_t *sa;

  /* If operation is already mapped, it is mappable (though not changable) */
  if (so->processor) return 1;

  //lprintf(STREAM(scl.crawl), "Checking %d for mappability", so->id);

  /* If any streams have unmappable producers, operation is not mappable */
  while (tblget(&so->arguments, id = tblgetnexti(&so->arguments, id), &sa))
  {
    //lprintf(STREAM(scl.crawl), "Checking %d arg %d", so->id, sa->id);
    //lprintf(STREAM(scl.crawl), "intent %d", sa->intent);
    //lprintf(STREAM(scl.crawl), "type %d", sa->instance->variable->type);
    //lprintf(STREAM(scl.crawl), "producer %p", sa->instance->variable->producer);
    //lprintf(STREAM(scl.crawl), "intent %d", sa->intent);
    if (sa->instance->variable->type == SC_VAR_STREAM &&
	sa->intent == SC_INTENT_IN &&
	(!sa->instance->variable->producer ||
	 !scMapIsOperationMappable(sa->instance->variable->producer->operation)))
    {
      lprintf(STREAM(scl.crawl), "%d is not mappable because of arg %d", so->id, sa->id);
      return 0;
    }
  }
                 
  //lprintf(STREAM(scl.crawl), "%d is mappable", so->id);
                    
  return 1;
}
